import {NotFoundError, t} from 'elysia'
import {ElysiaApp} from '..'
import {ForbiddenError, UnauthorizedError, createToken, encrypt, removeToken} from '../utils/auth'
import {groupBy, isEmpty, uniqBy} from 'lodash'
import {addLoginLog, getLoginLog, updLoginLog} from '../utils/dw'
import {and, eq, inArray, ne} from 'drizzle-orm'
import {sysMenu, sysRoleMenu, sysUser, sysUserRole} from '../schema/schema'

export default (app: ElysiaApp) =>
  app
    .post(
      '/login',
      async ({db, body, jwt, redis, headers, store: {ip, start}}) => {
        // 判断用户信息
        const _user = await db.query.sysUser.findFirst({
          columns: {id: true, password: true, pSalt: true, iSalt: true, username: true},
          where: eq(sysUser.username, body.username),
        })
        if (!_user) throw new NotFoundError('用户名不存在')
        const sign = encrypt(body.password, _user.pSalt, _user.iSalt)
        if (sign !== _user.password) throw new ForbiddenError('用户名或密码错误')
        // 判断用户是否有角色
        const roleIds = (
          await db.query.sysUserRole.findMany({
            columns: {roleId: true},
            where: eq(sysUserRole.userId, _user.id),
          })
        ).map(e => e.roleId)
        if (isEmpty(roleIds)) throw new ForbiddenError('该用户未设置任何角色，无法登录')
        // 判断用户角色是否包含菜单
        const menus = (
          await db
            .select({menuId: sysRoleMenu.menuId})
            .from(sysRoleMenu)
            .leftJoin(sysMenu, and(inArray(sysRoleMenu.roleId, roleIds), ne(sysMenu.type, 2)))
        ).map(v => v.menuId)
        if (isEmpty(menus)) throw new ForbiddenError('该用户无任何菜单权限，请联系管理员')
        // 登录成功
        redis.set(`user:sign:${_user.id}`, _user.pSalt)
        const {token, expire} = await createToken(jwt, redis, _user, roleIds)
        const {token: refreshToken, expire: refreshExpire} = await createToken(jwt, redis, _user, roleIds, 'refreshExpire', true)
        // 在返回数据前，将用户上一次登录的状态置为0
        updLoginLog(db, {userId: _user.id, online: 0})
        // 添加登录成功日志
        addLoginLog(db, {...(await getLoginLog(db, headers['user-agent'] as string, ip, redis)), status: 1, userId: _user.id, online: 1})
        return {token, expire, refreshToken, refreshExpire}
      },
      {
        body: t.Object({
          username: t.String({maxLength: 20}),
          password: t.String(),
        }),
      },
    )
    .get('/getLoginUser', async ({db, store: {curUser}}) => {
      const [user, menus] = await Promise.all([
        await db.query.sysUser.findFirst({columns: {id: true, name: true, avatar: true}, where: eq(sysUser.id, curUser!.userId)}),
        await db
          .select({id: sysMenu.id, pid: sysMenu.pid, name: sysMenu.name, perms: sysMenu.perms, icon: sysMenu.icon, type: sysMenu.type})
          .from(sysRoleMenu)
          .leftJoin(sysMenu, eq(sysRoleMenu.menuId, sysMenu.id))
          .where(inArray(sysRoleMenu.roleId, curUser!.roleIds)),
      ])
      const roleMenus = groupBy(uniqBy(menus, 'id'), e => e.type !== 2)
      return {
        ...user,
        menus: roleMenus.true,
        perms: roleMenus.false?.map(e => e?.perms) ?? [],
      }
    })
    .post(
      '/refreshToken',
      async ({body, jwt, db, redis}) => {
        const decoded = await jwt.verify(body.refreshToken)
        if (decoded && decoded['isRefresh']) {
          delete decoded['exp']
          delete decoded['iat']
          const {roleIds, isRefresh, ...user} = decoded
          const _user = await db.query.sysUser.findFirst({where: eq(sysUser.id, user.userId)})
          if (!_user || _user.pSalt !== user.sign) {
            await updLoginLog(db, {userId: user.userId, online: 0})
            // 用户密码已经更改
            throw new UnauthorizedError('当前用户密码已更改，请重新登录')
          }
          return await createToken(jwt, redis, {id: user.userId, username: user.username, pSalt: user.sign}, roleIds)
        }
      },
      {
        body: t.Object({
          refreshToken: t.String(),
        }),
      },
    )
    .get('/logout', async ({db, redis, store: {curUser}}) => {
      await removeToken(db, redis, curUser!.userId)
      return '登出成功'
    })
    .post(
      '/upload',
      async ({body: {file}}) => {
        const requireId = `upload_${Date.now()}.${Math.random()}`
        return file.map((e, index) => {
          const ext = e.name.split('.').pop()!
          const path = `/${requireId}.${index}.${ext}`
          Bun.write(`static/upload/${path}`, e)
          return {path}
        })
      },
      {
        body: t.Object({
          file: t.Files(),
        }),
      },
    )
    .get(
      '/file/:path',
      async ({params: {path}}) => {
        const file = Bun.file(`static/upload/${path}`)
        if (await file.exists()) return file
        throw new NotFoundError('文件不存在')
      },
      {
        params: t.Object({
          path: t.String(),
        }),
      },
    )
